Analysing train company tweets

This notebook details the process of analysing tweets by train company accounts.

We are interested in the following questions:

Import the data

First we need to import the data, which is over 100,000 tweets. There are a few ways of doing this: by using the link to the latest CSV, online…

…or by using the API endpoint which provides it in JSON:

You can also use this to get the results of SQL queries:

countbyaccount <- jsonlite::fromJSON("https://premium.scraperwiki.com/3epab2y/iyjilhnvxo69dnp/sql/?q=%20select%20count(*)%2C%20account%0A%20from%20traincompanytweets%20%0A%20group%20by%20account%0A%20order%20by%20count(*)%20desc")
countbyaccount

…Or by importing a local file (the disadvantage of this is that it is not a live link to the latest data):

#Store the filename
csvfile = "traincompanytweets.csv"
tweets <- read.csv(csvfile)

Next some overview of accounts:

table(tweets$account)

                       ArrivaTW        c2c_Rail chilternrailway   Elizabethline 
            306            3232            4199            3900             638 
       EMTrains     ga_mainline     ga_regional   ga_westanglia        GNRailUK 
           4525            3660            3654            3673            4665 
  greateranglia         GWRHelp HeathrowExpress   LDNOverground            LNER 
           6034            6001            3219            3228            3208 
      LNRailway      merseyrail nationalrailenq     networkrail  northernassist 
           3224            3204            3243            3221            3233 
    NRE_TfLRail        ScotRail      Se_Railway  SouthernRailUK    Stansted_Exp 
           1919            3206            3219            5329            4081 
        SW_Help      SW_Railway         TfLRail        TLRailUK       tlupdates 
           3225            3220            3245            5054            5066 
      TPEAssist    VirginTrains  WestMidRailway 
           3214            3243            3230 

And a summary of columns:

summary(tweets)
                          tweetdate     
 Fri Nov 23 06:29:33 +0000 2018:    19  
 Mon Oct 29 08:56:24 +0000 2018:    11  
 Mon Oct 08 09:46:53 +0000 2018:     8  
 Thu Nov 01 11:44:43 +0000 2018:     8  
 Fri Nov 23 11:12:35 +0000 2018:     7  
 Sun Nov 25 05:59:57 +0000 2018:     7  
 (Other)                       :117458  
                                                                                                                                                 tweettxt     
 "Services are running on time and our team will be here to help from 7am (Mon-Fri) or 8am (Sat-Sun) #hexupdates"                                    :   123  
 "Heathrow Express services are running on time #hexupdates"                                                                                         :    68  
 "Travelling home with us? Please check before you travel for live updates on your journey -  https://t.co/NWZnb59sUi"                               :    47  
 "Services are running on time this morning and our team will be here to help from 7am (Mon-Fri) or 8am (Sat-Sun) #hexupdates"                       :    40  
 "RT @networkrail: For any emergencies on the railway 24 hours a day call our helpline on 03457 11 41 41 or @BTP on 0800 40 50 40 / emergency\\u2026":    29  
 "If you've been delayed on our services in excess of 30 mins today, you can find details of how to claim at https://t.co/8mjQWiz2qf"                :    27  
 (Other)                                                                                                                                             :117184  
                                                        tweeturl     
 https://twitter.com/_____eleanor/status/1047394623679864833:     1  
 https://twitter.com/____LP____/status/1063138290029789185  :     1  
 https://twitter.com/___al94/status/1074970095049412608     :     1  
 https://twitter.com/___EmmaLou/status/1065522972004360197  :     1  
 https://twitter.com/___jcx/status/1064481100544843776      :     1  
 https://twitter.com/___megss/status/1066080101115281411    :     1  
 (Other)                                                    :117512  
               name          tweetid                  screenname   
 GA West Anglia  : 3666   Min.   :5.960e+17   ga_westanglia: 3666  
 GA Regional     : 3653   1st Qu.:1.053e+18   ga_regional  : 3653  
 GA Mainline     : 3650   Median :1.063e+18   ga_mainline  : 3650  
 Stansted Express: 3292   Mean   :1.053e+18   Stansted_Exp : 3293  
 Greater Anglia  : 2082   3rd Qu.:1.067e+18   greateranglia: 2144  
 NRE TfLRail     : 1804   Max.   :1.075e+18   NRE_TfLRail  : 1804  
 (Other)         :99371                       (Other)      :99308  
           account     
 greateranglia : 6034  
 GWRHelp       : 6001  
 SouthernRailUK: 5329  
 tlupdates     : 5066  
 TLRailUK      : 5054  
 GNRailUK      : 4665  
 (Other)       :85369  

 Filter down

Some accounts publish too infrequently and need to be removed:

#Remove tweets by two accounts:
tweets <- subset(tweets, tweets$account != "Elizabethline")
tweets <- subset(tweets, tweets$account != "NRE_TfLRail")

Let’s check it again:

sqldf::sqldf("SELECT count(*) as tweets, account 
             FROM tweets
             GROUP BY account
             ORDER BY tweets")

That’s good: we only have accounts with more than 3000 tweets. What about those with no account name?

sqldf::sqldf("SELECT *
             FROM tweets 
             WHERE account = ''
             LIMIT 10")

Some are identifiable but most would need further scraping, so let’s remove them for the purposes of this analysis.

tweets <- subset(tweets, tweets$account != "")

Using lubridate to clean dates

Because the dates are stored as strings, we need to extract and convert parts of those strings into data that can be understood as dates by R. The lubridate library is designed for this:

#Install the tidyverse package
library(tidyverse)
package ‘tidyverse’ was built under R version 3.4.2── Attaching packages ───────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 2.2.1     ✔ purrr   0.2.5
✔ tibble  1.4.2     ✔ dplyr   0.7.6
✔ tidyr   0.8.1     ✔ stringr 1.2.0
✔ readr   1.1.1     ✔ forcats 0.3.0
package ‘tibble’ was built under R version 3.4.3package ‘tidyr’ was built under R version 3.4.4package ‘purrr’ was built under R version 3.4.4package ‘dplyr’ was built under R version 3.4.4package ‘forcats’ was built under R version 3.4.3── Conflicts ──────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
#Activate the lubridate library which is part of that, 
library(lubridate)
package ‘lubridate’ was built under R version 3.4.4
Attaching package: ‘lubridate’

The following object is masked from ‘package:base’:

    date
#Store an example so we can see it to work with
dateeg <- "Fri Oct 05 17:23:36 +0000 2018"
#Count how many characters in that, so we can use substr
nchar(dateeg)
[1] 30
#We could extract parts of the date as strings, based on their position...
tweets$tweetweekday <- substr(tweets$tweetdate,0,3)
tweets$tweetmonth <- substr(tweets$tweetdate,5,7)
#Repeat for the others and store them as numeric to help with using later
tweets$tweetyear <- as.numeric(substr(tweets$tweetdate,26,30))
tweets$tweetday <- as.numeric(substr(tweets$tweetdate,9,10))
tweets$tweethour <- as.numeric(substr(tweets$tweetdate,12,13))
tweets$tweetmin <- as.numeric(substr(tweets$tweetdate,15,16))
tweets$tweetsec <- as.numeric(substr(tweets$tweetdate,18,19))
#But we could also extract them as numerical values using lubridate
#Unfortunately, the format - day-month-day - doesn't fit a pre-existing function so we need to create a format and then pull that in using parse_date
tweets$fulldate <- paste(substr(tweets$tweetdate,9,10),tweets$tweetmonth,tweets$tweetyear,tweets$tweethour,substr(tweets$tweetdate,15,16),tweets$tweetsec, sep="-")
#Test 05-Oct-2018-17-23-36
lubridate::parse_date_time("05-Oct-2018-17-23-36", "%d-%b-%Y-%H-%M-%S")
unknown timezone 'zone/tz/2018g.1.0/zoneinfo/Europe/London'
[1] "2018-10-05 17:23:36 UTC"
tweets$realdate <- parse_datetime(tweets$fulldate, "%d-%b-%Y-%H-%M-%S")

Now we should be able to identify the earliest and latest tweets:

min(tweets$realdate)
[1] "2013-12-23 15:02:52 UTC"
max(tweets$realdate)
[1] "2018-12-19 08:11:19 UTC"

And show the earliest ones:

attach(tweets)
head(tweets[order(realdate),])
detach(tweets)

These seem to be mostly from @tlupdates - “Retweets for fellow commuters sharing travel info built by @awhitehouse. (Official TL account is @TLRailUK and complaints page http://bit.ly/tl-complaints .)”

We need to filter that out too:

#remove tlupdates from the data
tweets <- subset(tweets, tweets$account != "tlupdates")

And re-run:

attach(tweets)
head(tweets[order(realdate),])
detach(tweets)

This time HeathrowExpress seems to have the oldest, but they’re all 2018 which is better.

Let’s create a filtered subset which only covers a certain period.

Filtering on date: starting from Nov 20

We know that northernassist tweets start on Nov 20 so we could take that as a starting date:

tweetsnov20on <- tweets %>%
  select(tweetdate, tweettxt, tweeturl, name, screenname, account, realdate) %>%
  filter(realdate >= as.Date("2018-11-20") )
package ‘bindrcpp’ was built under R version 3.4.4
table(tweetsnov20on$account)

                       ArrivaTW        c2c_Rail chilternrailway   Elizabethline 
              0               0            1640            1250               0 
       EMTrains     ga_mainline     ga_regional   ga_westanglia        GNRailUK 
           2081             668             584             635            1936 
  greateranglia         GWRHelp HeathrowExpress   LDNOverground            LNER 
           5675            5092             116             148            1960 
      LNRailway      merseyrail nationalrailenq     networkrail  northernassist 
            474             451            1740             356            3233 
    NRE_TfLRail        ScotRail      Se_Railway  SouthernRailUK    Stansted_Exp 
              0            2117            2588            3373            1251 
        SW_Help      SW_Railway         TfLRail        TLRailUK       tlupdates 
           2839             539             394            2679               0 
      TPEAssist    VirginTrains  WestMidRailway 
            500            3143             564 

Which terms are most common?

We are expecting that phrases like ‘crew’ or ‘tracks’ might occur particularly frequently. Let’s find out which ones.

First, we need to export the column of keywords:

#This is based on steps outlined in a [blog post by John Victor Anderson](http://johnvictoranderson.org/?p=115). 
write.csv(tweetsnov20on$tweettxt, 'tweetsastext.txt')

Now we re-import that data as a character object using scan:

tweettext <- scan('tweetsastext.txt', what="char", sep=",")
# We convert all text to lower case to prevent any case sensitive issues with counting
tweettext <- tolower(tweettext)
#Repace quotes because each tweet starts and ends with one
tweettext <- gsub('"', '', tweettext)
#Replace new line code with a space
tweettext <- gsub('\n', ' ', tweettext)
#Unescape HTML - first activate the htmltools package
library(htmltools)
#Then run the htmlEscape function
tweettext <- htmltools::htmlEscape(tweettext)
#This doesn't seem to work 100%

We now need to put this through a series of conversions before we can generate a table:

#Split the text on every space
tweettext.split <- strsplit(tweettext, " ")
#Create a vector
tweettextvec <- unlist(tweettext.split)
#Convert that to a table
tweettexttable <- table(tweettextvec)
#remove the objects created that we no longer need
rm(tweettext.split, tweettextvec)

That table is enough to create a CSV from:

write.csv(tweettexttable, 'tweettexttable.csv')
#read it back in
tweetdata <- read.csv('tweettexttable.csv')
summary(tweetdata)
#rename the columns
colnames(tweetdata) <- c('index', 'word', 'freq' )
summary(tweetdata)

Removing stopwords

We could strip out stopwords from our data using tidytext’s stop words.

#Install tidytext which is needed for get_stopwords() 
library(tidytext)
#Use anti_join with stopwords fetched using get_stopwords to remove those stopwords from tweetdata and put in new object
cleaned_tweetdata <- tweetdata %>%
  anti_join(get_stopwords())

Let’s use a simpler approach with gsub to remove punctuation

#gsub is used to replace any comma with nothing ""
#the results are used to create a new column
cleaned_tweetdata$wordnopunc <- gsub(",","",cleaned_tweetdata$word)
#That new column is overwritten with each new clean
cleaned_tweetdata$wordnopunc <- gsub("-","",cleaned_tweetdata$wordnopunc)
cleaned_tweetdata$wordnopunc <- gsub("!","",cleaned_tweetdata$wordnopunc)
cleaned_tweetdata$wordnopunc <- gsub("'","",cleaned_tweetdata$wordnopunc)
cleaned_tweetdata$wordnopunc <- gsub('"',"",cleaned_tweetdata$wordnopunc)
#This has to be escaped or it replaces all characters
cleaned_tweetdata$wordnopunc <- gsub("\\.","",cleaned_tweetdata$wordnopunc)
cleaned_tweetdata$wordnopunc <- gsub("\\?","",cleaned_tweetdata$wordnopunc)
cleaned_tweetdata$word2 <- NULL

SQL query: most common words

We can bring the most frequent words to the top using sqldf:

sqldf::sqldf('SELECT wordnopunc, freq 
             FROM cleaned_tweetdata
             ORDER BY freq DESC
             LIMIT 100')

Sorry seems to be the hardest word - but it’s not the only one

‘Sorry’ is just one type of apology. Let’s add a new column that identifies variants:

#Generate a TRUE/FALSE column which returns true if 'sorry' or 'apol' is anywhere in the text
cleaned_tweetdata$sorry <- grepl("sorry|apol", cleaned_tweetdata$word)
#Because 'sorry' is a TRUE/FALSE logical column, we need to use 1 or 0 to indicate that in sqldf
#See https://stackoverflow.com/questions/41433927/sqldf-cant-read-logical-vectors-in-r
sqldf::sqldf("SELECT * FROM cleaned_tweetdata 
             WHERE sorry = 1
             ORDER BY freq DESC")

What about a grand total:

#Use sum to add up all the results and just show that
sqldf::sqldf("SELECT sum(freq), sorry FROM cleaned_tweetdata 
             GROUP BY sorry
             ORDER BY freq DESC")

Those totals refer to individual words rather than tweets, so we could repeat it for the tweets instead:

#Generate a TRUE/FALSE column which returns true if 'sorry' or 'apol' is anywhere in the text
tweetsnov20on$sorry <- grepl("sorry|apol", ignore.case = T, tweetsnov20on$tweettxt)
#Because 'sorry' is a TRUE/FALSE logical column, we need to use 1 or 0 to indicate that in sqldf
#See https://stackoverflow.com/questions/41433927/sqldf-cant-read-logical-vectors-in-r
sorrytotals <- sqldf::sqldf("SELECT count(*), sorry FROM tweetsnov20on 
             GROUP BY sorry
             ")
sorrytotals

As a percentage of tweets…

sorrytotals$`count(*)`[2]/sum(sorrytotals$`count(*)`)*100

A breakdown by account

We can group by account to see which ones are most sorry:

sorrybyaccount <- sqldf::sqldf("SELECT count(*), account, sorry FROM tweetsnov20on
             GROUP BY account, sorry
             ")
sorrybyaccount
write_csv(sorrybyaccount, "sorrybyaccount.csv")

The worst is Northern, so let’s import that data, then export it as a CSV to be analysed manually in Excel if we need:

northernjson <- "https://premium.scraperwiki.com/3epab2y/iyjilhnvxo69dnp/sql/?q=%20select%20*%0A%20from%20traincompanytweets%20%0A%20where%20account%20is%20%22northernassist%22%0A%20"
northerntweets <- jsonlite::fromJSON(northernjson)
write_csv(northerntweets,"northerntweets.csv")

The worst day of the week

Or the worst day of the week

#LEFT is called LEFTSTR in sqldf: https://stackoverflow.com/questions/31843373/how-to-use-right-left-to-split-a-variable-in-sqldf-as-in-leftx-n
sorrybyweekday <- sqldf::sqldf("SELECT count(*), LEFTSTR(tweetdate, 3) AS tweetday, sorry FROM tweetsnov20on
             GROUP BY tweetday, sorry
             ")
sorrybyweekday
write_csv(sorrybyweekday, "sorrybyweekday.csv")

A breakdown over time

We can use the same approach to pull out each month’s apologies:

#SUBSTR is the sqldf function to extract from the middle of text, like MID in Excel
sorrybymonth <- sqldf::sqldf("SELECT count(*), SUBSTR(tweetdate, 5,3) AS tweetday, sorry FROM tweetsnov20on
             GROUP BY tweetday, sorry
             ")
sorrybymonth
write_csv(sorrybymonth, "sorrybymonth.csv")

There are 12 months there, so that means different accounts’ tweets cover different periods:

#SUBSTR is the sqldf function to extract from the middle of text, like MID in Excel
accountbymonth <- sqldf::sqldf("SELECT count(*), SUBSTR(tweetdate, 5,3) AS tweetday, account FROM tweetsnov20on
             GROUP BY tweetday, account ORDER by account
             ")
accountbymonth
write_csv(accountbymonth, "accountbymonth.csv")

Other words: crew, late, london

Let’s add a new column that looks for ‘crew’ or ‘staff’

#Generate a TRUE/FALSE column which returns true if 'crew' or 'staff' is anywhere in the text
tweetsnov20on$crew <- grepl("crew|staff", ignore.case = T,tweetsnov20on$tweettxt)
crewtotals <- sqldf::sqldf("SELECT count(*), crew FROM tweetsnov20on 
             GROUP BY crew
             ")
crewtotals

Or ‘late’ or ‘delay’


tweetsnov20on$late <- grepl("late|delay", ignore.case = T, tweetsnov20on$tweettxt)
latetotals <- sqldf::sqldf("SELECT count(*), late FROM tweetsnov20on 
             GROUP BY late
             ")
latetotals

Or ‘fault’ or ‘problem’

tweetsnov20on$fault <- grepl("fault|problem", ignore.case = T, tweetsnov20on$tweettxt)
faulttotals <- sqldf::sqldf("SELECT count(*), fault FROM tweetsnov20on 
             GROUP BY fault
             ")
faulttotals

Or ‘carriage’ or ‘coaches’

tweetsnov20on$carriage <- grepl("carriage|coach", ignore.case = T, tweetsnov20on$tweettxt)
carriagetotals <- sqldf::sqldf("SELECT count(*), carriage FROM tweetsnov20on 
             GROUP BY carriage
             ")
carriagetotals

Or ‘compensation’ or ‘repay’ (delay repay)

#Generate a TRUE/FALSE column which returns true if 'crew' or 'staff' is anywhere in the text
tweetsnov20on$compensation <- grepl("compensat|repay", ignore.case = T, tweetsnov20on$tweettxt)
compensationtotals <- sqldf::sqldf("SELECT count(*), compensation FROM tweetsnov20on 
             GROUP BY compensation
             ")
compensationtotals

Or ‘london’

tweetsnov20on$london <- grepl("ondon", ignore.case = T, tweetsnov20on$tweettxt)
londontotals <- sqldf::sqldf("SELECT count(*), london FROM tweetsnov20on 
             GROUP BY london
             ")
londontotals

Or ‘vandalism’

tweetsnov20on$vandal <- grepl("vandal", ignore.case = T, tweetsnov20on$tweettxt)
vandaltotals <- sqldf::sqldf("SELECT count(*), vandal FROM tweetsnov20on 
             GROUP BY vandal
             ")
vandaltotals

Compensation: a breakdown by account

Part of our story is about compensation, so it might be useful to group by account to see which ones mention that most:

compensationbyaccount <- sqldf::sqldf("SELECT count(*), account, compensation FROM tweetsnov20on
             GROUP BY account, compensation
             ")
compensationbyaccount
write_csv(compensationbyaccount, "compensationbyaccount.csv")
LS0tCnRpdGxlOiAiQW5hbHlzaW5nIHRyYWluIGNvbXBhbnkgdHdlZXRzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIEFuYWx5c2luZyB0cmFpbiBjb21wYW55IHR3ZWV0cwoKVGhpcyBub3RlYm9vayBkZXRhaWxzIHRoZSBwcm9jZXNzIG9mIGFuYWx5c2luZyB0d2VldHMgYnkgdHJhaW4gY29tcGFueSBhY2NvdW50cy4KCldlIGFyZSBpbnRlcmVzdGVkIGluIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb25zOgoKKiBXaGljaCB0ZXJtcyBhcHBlYXIgbW9zdCBmcmVxdWVudGx5IGluIHRoZSB0d2VldHM/CiogSG93IGRvZXMgdGhhdCB0ZXJtIGZyZXF1ZW5jeSBjaGFuZ2Ugb3ZlciB0aW1lPwoqIFdobyB1c2VzIHRob3NlIHRlcm1zIG1vc3QvbGVhc3Q/CiogV2hpY2ggYWNjb3VudHMgYXJlIG1vc3QgYWN0aXZlIChpbiB0ZXJtcyBvZiBiZWluZyByZXNwb25kZWQgdG8pPwoKIyMgSW1wb3J0IHRoZSBkYXRhCgpGaXJzdCB3ZSBuZWVkIHRvIGltcG9ydCB0aGUgZGF0YSwgd2hpY2ggaXMgb3ZlciAxMDAsMDAwIHR3ZWV0cy4gVGhlcmUgYXJlIGEgZmV3IHdheXMgb2YgZG9pbmcgdGhpczogYnkgdXNpbmcgdGhlIGxpbmsgdG8gdGhlIGxhdGVzdCBDU1YsIG9ubGluZS4uLgoKYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiNET04nVCBydW4gdGhpcyBjb2RlIC0gaXQgd2lsbCB0YWtlIHRvbyBsb25nIQojc3RvcmUgdGhlIGxvY2F0aW9uIG9mIHRoZSBDU1YKY3N2dXJsIDwtICJodHRwczovL3ByZW1pdW0uc2NyYXBlcndpa2kuY29tLzNlcGFiMnkvaXlqaWxobnZ4bzY5ZG5wL2NnaS1iaW4vY3N2L3RyYWluY29tcGFueXR3ZWV0cy5jc3YiCiNpbXBvcnQgdGhlIGRhdGEKdHdlZXRzZGF0YSA8LSByZWFkLmNzdihjc3Z1cmwsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYGBgCgouLi5vciBieSB1c2luZyB0aGUgQVBJIGVuZHBvaW50IHdoaWNoIHByb3ZpZGVzIGl0IGluIEpTT046CgpgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KI0Rvbid0IHJ1biB0aGlzIGNvZGUgZWl0aGVyLCBhcyBpdCB3aWxsIG9ubHkgcHJvdmlkZSBhIHNhbXBsZSB0byB3b3JrIHdpdGgKI2luc3RhbGwucGFja2FnZXMoImpzb25saXRlIikKI0FjdGl2YXRlIHRoZSBqc29uIHBhY2thZ2UgLSB1bmNvbW1lbnQgdGhlIGxpbmUgYWJvdmUgdG8gaW5zdGFsbCBpdCBmaXJzdCBpZiB5b3UgZ2V0IGFuIGVycm9yCmxpYnJhcnkoanNvbmxpdGUpCmxpYnJhcnkoaHR0cikKI1N0b3JlIHRoZSBVUkwgd2hpY2ggcXVlcmllcyB0aGUgc2NyYXBlcidzIEFQSSBmb3IgYWxsIHR3ZWV0cwojVGhpcyBpcyBsaW1pdGVkIHRvIDEwMCBmb3IgdGVzdGluZyBwdXJwb3Nlcwpqc29udXJsIDwtICJodHRwczovL3ByZW1pdW0uc2NyYXBlcndpa2kuY29tLzNlcGFiMnkvaXlqaWxobnZ4bzY5ZG5wL3NxbC8/cT0lMjBzZWxlY3QlMjAqJTBBJTIwZnJvbSUyMHRyYWluY29tcGFueXR3ZWV0cyUyMCUwQWxpbWl0JTIwMTAwIgp0d2VldHMxMDAgPC0ganNvbmxpdGU6OmZyb21KU09OKGpzb251cmwpCmBgYAoKWW91IGNhbiBhbHNvIHVzZSB0aGlzIHRvIGdldCB0aGUgcmVzdWx0cyBvZiBTUUwgcXVlcmllczoKCmBgYHtyfQpjb3VudGJ5YWNjb3VudCA8LSBqc29ubGl0ZTo6ZnJvbUpTT04oImh0dHBzOi8vcHJlbWl1bS5zY3JhcGVyd2lraS5jb20vM2VwYWIyeS9peWppbGhudnhvNjlkbnAvc3FsLz9xPSUyMHNlbGVjdCUyMGNvdW50KCopJTJDJTIwYWNjb3VudCUwQSUyMGZyb20lMjB0cmFpbmNvbXBhbnl0d2VldHMlMjAlMEElMjBncm91cCUyMGJ5JTIwYWNjb3VudCUwQSUyMG9yZGVyJTIwYnklMjBjb3VudCgqKSUyMGRlc2MiKQpjb3VudGJ5YWNjb3VudApgYGAKCgouLi5PciBieSBpbXBvcnRpbmcgYSBsb2NhbCBmaWxlICh0aGUgZGlzYWR2YW50YWdlIG9mIHRoaXMgaXMgdGhhdCBpdCBpcyBub3QgYSBsaXZlIGxpbmsgdG8gdGhlIGxhdGVzdCBkYXRhKToKCmBgYHtyfQojU3RvcmUgdGhlIGZpbGVuYW1lCmNzdmZpbGUgPSAidHJhaW5jb21wYW55dHdlZXRzLmNzdiIKdHdlZXRzIDwtIHJlYWQuY3N2KGNzdmZpbGUpCmBgYAoKTmV4dCBzb21lIG92ZXJ2aWV3IG9mIGFjY291bnRzOgoKYGBge3J9CnRhYmxlKHR3ZWV0cyRhY2NvdW50KQpgYGAKCkFuZCBhIHN1bW1hcnkgb2YgY29sdW1uczoKCmBgYHtyfQpzdW1tYXJ5KHR3ZWV0cykKYGBgCiMjwqBGaWx0ZXIgZG93bgoKU29tZSBhY2NvdW50cyBwdWJsaXNoIHRvbyBpbmZyZXF1ZW50bHkgYW5kIG5lZWQgdG8gYmUgcmVtb3ZlZDoKCmBgYHtyfQojUmVtb3ZlIHR3ZWV0cyBieSB0d28gYWNjb3VudHM6CnR3ZWV0cyA8LSBzdWJzZXQodHdlZXRzLCB0d2VldHMkYWNjb3VudCAhPSAiRWxpemFiZXRobGluZSIpCnR3ZWV0cyA8LSBzdWJzZXQodHdlZXRzLCB0d2VldHMkYWNjb3VudCAhPSAiTlJFX1RmTFJhaWwiKQpgYGAKCkxldCdzIGNoZWNrIGl0IGFnYWluOgoKYGBge3J9CnNxbGRmOjpzcWxkZigiU0VMRUNUIGNvdW50KCopIGFzIHR3ZWV0cywgYWNjb3VudCAKICAgICAgICAgICAgIEZST00gdHdlZXRzCiAgICAgICAgICAgICBHUk9VUCBCWSBhY2NvdW50CiAgICAgICAgICAgICBPUkRFUiBCWSB0d2VldHMiKQpgYGAKClRoYXQncyBnb29kOiB3ZSBvbmx5IGhhdmUgYWNjb3VudHMgd2l0aCBtb3JlIHRoYW4gMzAwMCB0d2VldHMuIFdoYXQgYWJvdXQgdGhvc2Ugd2l0aCBubyBhY2NvdW50IG5hbWU/CgpgYGB7cn0Kc3FsZGY6OnNxbGRmKCJTRUxFQ1QgKgogICAgICAgICAgICAgRlJPTSB0d2VldHMgCiAgICAgICAgICAgICBXSEVSRSBhY2NvdW50ID0gJycKICAgICAgICAgICAgIExJTUlUIDEwIikKYGBgCgpTb21lIGFyZSBpZGVudGlmaWFibGUgYnV0IG1vc3Qgd291bGQgbmVlZCBmdXJ0aGVyIHNjcmFwaW5nLCBzbyBsZXQncyByZW1vdmUgdGhlbSBmb3IgdGhlIHB1cnBvc2VzIG9mIHRoaXMgYW5hbHlzaXMuCgpgYGB7cn0KdHdlZXRzIDwtIHN1YnNldCh0d2VldHMsIHR3ZWV0cyRhY2NvdW50ICE9ICIiKQpgYGAKCiMjIFVzaW5nIGx1YnJpZGF0ZSB0byBjbGVhbiBkYXRlcwoKQmVjYXVzZSB0aGUgZGF0ZXMgYXJlIHN0b3JlZCBhcyBzdHJpbmdzLCB3ZSBuZWVkIHRvIGV4dHJhY3QgYW5kIGNvbnZlcnQgcGFydHMgb2YgdGhvc2Ugc3RyaW5ncyBpbnRvIGRhdGEgdGhhdCBjYW4gYmUgdW5kZXJzdG9vZCBhcyBkYXRlcyBieSBSLiBUaGUgYGx1YnJpZGF0ZWAgbGlicmFyeSBpcyBbZGVzaWduZWQgZm9yIHRoaXNdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovZGF0YS1pbXBvcnQuaHRtbCNyZWFkci1kYXRldGltZXMpOgoKYGBge3J9CiNJbnN0YWxsIHRoZSB0aWR5dmVyc2UgcGFja2FnZQpsaWJyYXJ5KHRpZHl2ZXJzZSkKI0FjdGl2YXRlIHRoZSBsdWJyaWRhdGUgbGlicmFyeSB3aGljaCBpcyBwYXJ0IG9mIHRoYXQsIApsaWJyYXJ5KGx1YnJpZGF0ZSkKI1N0b3JlIGFuIGV4YW1wbGUgc28gd2UgY2FuIHNlZSBpdCB0byB3b3JrIHdpdGgKZGF0ZWVnIDwtICJGcmkgT2N0IDA1IDE3OjIzOjM2ICswMDAwIDIwMTgiCiNDb3VudCBob3cgbWFueSBjaGFyYWN0ZXJzIGluIHRoYXQsIHNvIHdlIGNhbiB1c2Ugc3Vic3RyCm5jaGFyKGRhdGVlZykKI1dlIGNvdWxkIGV4dHJhY3QgcGFydHMgb2YgdGhlIGRhdGUgYXMgc3RyaW5ncywgYmFzZWQgb24gdGhlaXIgcG9zaXRpb24uLi4KdHdlZXRzJHR3ZWV0d2Vla2RheSA8LSBzdWJzdHIodHdlZXRzJHR3ZWV0ZGF0ZSwwLDMpCnR3ZWV0cyR0d2VldG1vbnRoIDwtIHN1YnN0cih0d2VldHMkdHdlZXRkYXRlLDUsNykKI1JlcGVhdCBmb3IgdGhlIG90aGVycyBhbmQgc3RvcmUgdGhlbSBhcyBudW1lcmljIHRvIGhlbHAgd2l0aCB1c2luZyBsYXRlcgp0d2VldHMkdHdlZXR5ZWFyIDwtIGFzLm51bWVyaWMoc3Vic3RyKHR3ZWV0cyR0d2VldGRhdGUsMjYsMzApKQp0d2VldHMkdHdlZXRkYXkgPC0gYXMubnVtZXJpYyhzdWJzdHIodHdlZXRzJHR3ZWV0ZGF0ZSw5LDEwKSkKdHdlZXRzJHR3ZWV0aG91ciA8LSBhcy5udW1lcmljKHN1YnN0cih0d2VldHMkdHdlZXRkYXRlLDEyLDEzKSkKdHdlZXRzJHR3ZWV0bWluIDwtIGFzLm51bWVyaWMoc3Vic3RyKHR3ZWV0cyR0d2VldGRhdGUsMTUsMTYpKQp0d2VldHMkdHdlZXRzZWMgPC0gYXMubnVtZXJpYyhzdWJzdHIodHdlZXRzJHR3ZWV0ZGF0ZSwxOCwxOSkpCiNCdXQgd2UgY291bGQgYWxzbyBleHRyYWN0IHRoZW0gYXMgbnVtZXJpY2FsIHZhbHVlcyB1c2luZyBsdWJyaWRhdGUKI1VuZm9ydHVuYXRlbHksIHRoZSBmb3JtYXQgLSBkYXktbW9udGgtZGF5IC0gZG9lc24ndCBmaXQgYSBwcmUtZXhpc3RpbmcgZnVuY3Rpb24gc28gd2UgbmVlZCB0byBjcmVhdGUgYSBmb3JtYXQgYW5kIHRoZW4gcHVsbCB0aGF0IGluIHVzaW5nIHBhcnNlX2RhdGUKdHdlZXRzJGZ1bGxkYXRlIDwtIHBhc3RlKHN1YnN0cih0d2VldHMkdHdlZXRkYXRlLDksMTApLHR3ZWV0cyR0d2VldG1vbnRoLHR3ZWV0cyR0d2VldHllYXIsdHdlZXRzJHR3ZWV0aG91cixzdWJzdHIodHdlZXRzJHR3ZWV0ZGF0ZSwxNSwxNiksdHdlZXRzJHR3ZWV0c2VjLCBzZXA9Ii0iKQojVGVzdCAwNS1PY3QtMjAxOC0xNy0yMy0zNgpsdWJyaWRhdGU6OnBhcnNlX2RhdGVfdGltZSgiMDUtT2N0LTIwMTgtMTctMjMtMzYiLCAiJWQtJWItJVktJUgtJU0tJVMiKQp0d2VldHMkcmVhbGRhdGUgPC0gcGFyc2VfZGF0ZXRpbWUodHdlZXRzJGZ1bGxkYXRlLCAiJWQtJWItJVktJUgtJU0tJVMiKQpgYGAKCk5vdyB3ZSBzaG91bGQgYmUgYWJsZSB0byBpZGVudGlmeSB0aGUgZWFybGllc3QgYW5kIGxhdGVzdCB0d2VldHM6CgpgYGB7cn0KbWluKHR3ZWV0cyRyZWFsZGF0ZSkKbWF4KHR3ZWV0cyRyZWFsZGF0ZSkKYGBgCgpBbmQgc2hvdyB0aGUgZWFybGllc3Qgb25lczoKCmBgYHtyfQphdHRhY2godHdlZXRzKQpoZWFkKHR3ZWV0c1tvcmRlcihyZWFsZGF0ZSksXSkKZGV0YWNoKHR3ZWV0cykKYGBgCgpUaGVzZSBzZWVtIHRvIGJlIG1vc3RseSBmcm9tIEB0bHVwZGF0ZXMgLSAiUmV0d2VldHMgZm9yIGZlbGxvdyBjb21tdXRlcnMgc2hhcmluZyB0cmF2ZWwgaW5mbyBidWlsdCBieSBAYXdoaXRlaG91c2UuIChPZmZpY2lhbCBUTCBhY2NvdW50IGlzIEBUTFJhaWxVSyBhbmQgY29tcGxhaW50cyBwYWdlIGh0dHA6Ly9iaXQubHkvdGwtY29tcGxhaW50cyAuKSIKCldlIG5lZWQgdG8gZmlsdGVyIHRoYXQgb3V0IHRvbzoKCmBgYHtyfQojcmVtb3ZlIHRsdXBkYXRlcyBmcm9tIHRoZSBkYXRhCnR3ZWV0cyA8LSBzdWJzZXQodHdlZXRzLCB0d2VldHMkYWNjb3VudCAhPSAidGx1cGRhdGVzIikKYGBgCgpBbmQgcmUtcnVuOgoKYGBge3J9CmF0dGFjaCh0d2VldHMpCmhlYWQodHdlZXRzW29yZGVyKHJlYWxkYXRlKSxdKQpkZXRhY2godHdlZXRzKQpgYGAKClRoaXMgdGltZSBIZWF0aHJvd0V4cHJlc3Mgc2VlbXMgdG8gaGF2ZSB0aGUgb2xkZXN0LCBidXQgdGhleSdyZSBhbGwgMjAxOCB3aGljaCBpcyBiZXR0ZXIuCgpMZXQncyBjcmVhdGUgYSBmaWx0ZXJlZCBzdWJzZXQgd2hpY2ggb25seSBjb3ZlcnMgYSBjZXJ0YWluIHBlcmlvZC4KCiMjIEZpbHRlcmluZyBvbiBkYXRlOiBzdGFydGluZyBmcm9tIE5vdiAyMAoKV2Uga25vdyB0aGF0IG5vcnRoZXJuYXNzaXN0IHR3ZWV0cyBzdGFydCBvbiBOb3YgMjAgc28gd2UgY291bGQgdGFrZSB0aGF0IGFzIGEgc3RhcnRpbmcgZGF0ZToKCmBgYHtyfQp0d2VldHNub3YyMG9uIDwtIHR3ZWV0cyAlPiUKICBzZWxlY3QodHdlZXRkYXRlLCB0d2VldHR4dCwgdHdlZXR1cmwsIG5hbWUsIHNjcmVlbm5hbWUsIGFjY291bnQsIHJlYWxkYXRlKSAlPiUKICBmaWx0ZXIocmVhbGRhdGUgPj0gYXMuRGF0ZSgiMjAxOC0xMS0yMCIpICkKdGFibGUodHdlZXRzbm92MjBvbiRhY2NvdW50KQojRXhwb3J0IGFzIENTVgp3cml0ZS5jc3YodHdlZXRzbm92MjBvbiwidHdlZXRzbm92MjBvbi5jc3YiKQpgYGAKCgojIyBXaGljaCB0ZXJtcyBhcmUgbW9zdCBjb21tb24/CgpXZSBhcmUgZXhwZWN0aW5nIHRoYXQgcGhyYXNlcyBsaWtlICdjcmV3JyBvciAndHJhY2tzJyBtaWdodCBvY2N1ciBwYXJ0aWN1bGFybHkgZnJlcXVlbnRseS4gTGV0J3MgZmluZCBvdXQgd2hpY2ggb25lcy4KCkZpcnN0LCB3ZSBuZWVkIHRvIGV4cG9ydCB0aGUgY29sdW1uIG9mIGtleXdvcmRzOgoKYGBge3J9CiNUaGlzIGlzIGJhc2VkIG9uIHN0ZXBzIG91dGxpbmVkIGluIGEgW2Jsb2cgcG9zdCBieSBKb2huIFZpY3RvciBBbmRlcnNvbl0oaHR0cDovL2pvaG52aWN0b3JhbmRlcnNvbi5vcmcvP3A9MTE1KS4gCndyaXRlLmNzdih0d2VldHNub3YyMG9uJHR3ZWV0dHh0LCAndHdlZXRzYXN0ZXh0LnR4dCcpCmBgYAoKTm93IHdlIHJlLWltcG9ydCB0aGF0IGRhdGEgYXMgYSBjaGFyYWN0ZXIgb2JqZWN0IHVzaW5nIGBzY2FuYDoKCmBgYHtyfQp0d2VldHRleHQgPC0gc2NhbigndHdlZXRzYXN0ZXh0LnR4dCcsIHdoYXQ9ImNoYXIiLCBzZXA9IiwiKQojIFdlIGNvbnZlcnQgYWxsIHRleHQgdG8gbG93ZXIgY2FzZSB0byBwcmV2ZW50IGFueSBjYXNlIHNlbnNpdGl2ZSBpc3N1ZXMgd2l0aCBjb3VudGluZwp0d2VldHRleHQgPC0gdG9sb3dlcih0d2VldHRleHQpCiNSZXBhY2UgcXVvdGVzIGJlY2F1c2UgZWFjaCB0d2VldCBzdGFydHMgYW5kIGVuZHMgd2l0aCBvbmUKdHdlZXR0ZXh0IDwtIGdzdWIoJyInLCAnJywgdHdlZXR0ZXh0KQojUmVwbGFjZSBuZXcgbGluZSBjb2RlIHdpdGggYSBzcGFjZQp0d2VldHRleHQgPC0gZ3N1YignXG4nLCAnICcsIHR3ZWV0dGV4dCkKI1VuZXNjYXBlIEhUTUwgLSBmaXJzdCBhY3RpdmF0ZSB0aGUgaHRtbHRvb2xzIHBhY2thZ2UKbGlicmFyeShodG1sdG9vbHMpCiNUaGVuIHJ1biB0aGUgaHRtbEVzY2FwZSBmdW5jdGlvbgp0d2VldHRleHQgPC0gaHRtbHRvb2xzOjpodG1sRXNjYXBlKHR3ZWV0dGV4dCkKI1RoaXMgZG9lc24ndCBzZWVtIHRvIHdvcmsgMTAwJQpgYGAKCldlIG5vdyBuZWVkIHRvIHB1dCB0aGlzIHRocm91Z2ggYSBzZXJpZXMgb2YgY29udmVyc2lvbnMgYmVmb3JlIHdlIGNhbiBnZW5lcmF0ZSBhIHRhYmxlOgoKYGBge3J9CiNTcGxpdCB0aGUgdGV4dCBvbiBldmVyeSBzcGFjZQp0d2VldHRleHQuc3BsaXQgPC0gc3Ryc3BsaXQodHdlZXR0ZXh0LCAiICIpCiNDcmVhdGUgYSB2ZWN0b3IKdHdlZXR0ZXh0dmVjIDwtIHVubGlzdCh0d2VldHRleHQuc3BsaXQpCiNDb252ZXJ0IHRoYXQgdG8gYSB0YWJsZQp0d2VldHRleHR0YWJsZSA8LSB0YWJsZSh0d2VldHRleHR2ZWMpCiNyZW1vdmUgdGhlIG9iamVjdHMgY3JlYXRlZCB0aGF0IHdlIG5vIGxvbmdlciBuZWVkCnJtKHR3ZWV0dGV4dC5zcGxpdCwgdHdlZXR0ZXh0dmVjKQpgYGAKClRoYXQgdGFibGUgaXMgZW5vdWdoIHRvIGNyZWF0ZSBhIENTViBmcm9tOgoKYGBge3J9CndyaXRlLmNzdih0d2VldHRleHR0YWJsZSwgJ3R3ZWV0dGV4dHRhYmxlLmNzdicpCiNyZWFkIGl0IGJhY2sgaW4KdHdlZXRkYXRhIDwtIHJlYWQuY3N2KCd0d2VldHRleHR0YWJsZS5jc3YnKQpzdW1tYXJ5KHR3ZWV0ZGF0YSkKI3JlbmFtZSB0aGUgY29sdW1ucwpjb2xuYW1lcyh0d2VldGRhdGEpIDwtIGMoJ2luZGV4JywgJ3dvcmQnLCAnZnJlcScgKQpzdW1tYXJ5KHR3ZWV0ZGF0YSkKYGBgCiMjIyBSZW1vdmluZyBzdG9wd29yZHMKCldlIGNvdWxkIHN0cmlwIG91dCBzdG9wd29yZHMgZnJvbSBvdXIgZGF0YSBbdXNpbmcgYHRpZHl0ZXh0YCdzIHN0b3Agd29yZHNdKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQzNDQxODg0L3JlbW92aW5nLXN0b3Atd29yZHMtd2l0aC10aWR5dGV4dD9ycT0xKS4KCmBgYHtyfQojSW5zdGFsbCB0aWR5dGV4dCB3aGljaCBpcyBuZWVkZWQgZm9yIGdldF9zdG9wd29yZHMoKSAKbGlicmFyeSh0aWR5dGV4dCkKI1VzZSBhbnRpX2pvaW4gd2l0aCBzdG9wd29yZHMgZmV0Y2hlZCB1c2luZyBnZXRfc3RvcHdvcmRzIHRvIHJlbW92ZSB0aG9zZSBzdG9wd29yZHMgZnJvbSB0d2VldGRhdGEgYW5kIHB1dCBpbiBuZXcgb2JqZWN0CmNsZWFuZWRfdHdlZXRkYXRhIDwtIHR3ZWV0ZGF0YSAlPiUKICBhbnRpX2pvaW4oZ2V0X3N0b3B3b3JkcygpKQpgYGAKCkxldCdzIHVzZSBhIHNpbXBsZXIgYXBwcm9hY2ggd2l0aCBgZ3N1YmAgdG8gcmVtb3ZlIHB1bmN0dWF0aW9uCgpgYGB7cn0KI2dzdWIgaXMgdXNlZCB0byByZXBsYWNlIGFueSBjb21tYSB3aXRoIG5vdGhpbmcgIiIKI3RoZSByZXN1bHRzIGFyZSB1c2VkIHRvIGNyZWF0ZSBhIG5ldyBjb2x1bW4KY2xlYW5lZF90d2VldGRhdGEkd29yZG5vcHVuYyA8LSBnc3ViKCIsIiwiIixjbGVhbmVkX3R3ZWV0ZGF0YSR3b3JkKQojVGhhdCBuZXcgY29sdW1uIGlzIG92ZXJ3cml0dGVuIHdpdGggZWFjaCBuZXcgY2xlYW4KY2xlYW5lZF90d2VldGRhdGEkd29yZG5vcHVuYyA8LSBnc3ViKCItIiwiIixjbGVhbmVkX3R3ZWV0ZGF0YSR3b3Jkbm9wdW5jKQpjbGVhbmVkX3R3ZWV0ZGF0YSR3b3Jkbm9wdW5jIDwtIGdzdWIoIiEiLCIiLGNsZWFuZWRfdHdlZXRkYXRhJHdvcmRub3B1bmMpCmNsZWFuZWRfdHdlZXRkYXRhJHdvcmRub3B1bmMgPC0gZ3N1YigiJyIsIiIsY2xlYW5lZF90d2VldGRhdGEkd29yZG5vcHVuYykKY2xlYW5lZF90d2VldGRhdGEkd29yZG5vcHVuYyA8LSBnc3ViKCciJywiIixjbGVhbmVkX3R3ZWV0ZGF0YSR3b3Jkbm9wdW5jKQojVGhpcyBoYXMgdG8gYmUgZXNjYXBlZCBvciBpdCByZXBsYWNlcyBhbGwgY2hhcmFjdGVycwpjbGVhbmVkX3R3ZWV0ZGF0YSR3b3Jkbm9wdW5jIDwtIGdzdWIoIlxcLiIsIiIsY2xlYW5lZF90d2VldGRhdGEkd29yZG5vcHVuYykKY2xlYW5lZF90d2VldGRhdGEkd29yZG5vcHVuYyA8LSBnc3ViKCJcXD8iLCIiLGNsZWFuZWRfdHdlZXRkYXRhJHdvcmRub3B1bmMpCmNsZWFuZWRfdHdlZXRkYXRhJHdvcmQyIDwtIE5VTEwKYGBgCgoKCiMjIFNRTCBxdWVyeTogbW9zdCBjb21tb24gd29yZHMKCldlIGNhbiBicmluZyB0aGUgbW9zdCBmcmVxdWVudCB3b3JkcyB0byB0aGUgdG9wIHVzaW5nIGBzcWxkZmA6CgpgYGB7cn0Kc3FsZGY6OnNxbGRmKCdTRUxFQ1Qgd29yZG5vcHVuYywgZnJlcSAKICAgICAgICAgICAgIEZST00gY2xlYW5lZF90d2VldGRhdGEKICAgICAgICAgICAgIE9SREVSIEJZIGZyZXEgREVTQwogICAgICAgICAgICAgTElNSVQgMTAwJykKYGBgCgojIyBTb3JyeSBzZWVtcyB0byBiZSB0aGUgaGFyZGVzdCB3b3JkIC0gYnV0IGl0J3Mgbm90IHRoZSBvbmx5IG9uZQoKJ1NvcnJ5JyBpcyBqdXN0IG9uZSB0eXBlIG9mIGFwb2xvZ3kuIExldCdzIGFkZCBhIG5ldyBjb2x1bW4gdGhhdCBpZGVudGlmaWVzIHZhcmlhbnRzOgoKYGBge3J9CiNHZW5lcmF0ZSBhIFRSVUUvRkFMU0UgY29sdW1uIHdoaWNoIHJldHVybnMgdHJ1ZSBpZiAnc29ycnknIG9yICdhcG9sJyBpcyBhbnl3aGVyZSBpbiB0aGUgdGV4dApjbGVhbmVkX3R3ZWV0ZGF0YSRzb3JyeSA8LSBncmVwbCgic29ycnl8YXBvbCIsIGNsZWFuZWRfdHdlZXRkYXRhJHdvcmQpCiNCZWNhdXNlICdzb3JyeScgaXMgYSBUUlVFL0ZBTFNFIGxvZ2ljYWwgY29sdW1uLCB3ZSBuZWVkIHRvIHVzZSAxIG9yIDAgdG8gaW5kaWNhdGUgdGhhdCBpbiBzcWxkZgojU2VlIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQxNDMzOTI3L3NxbGRmLWNhbnQtcmVhZC1sb2dpY2FsLXZlY3RvcnMtaW4tcgpzcWxkZjo6c3FsZGYoIlNFTEVDVCAqIEZST00gY2xlYW5lZF90d2VldGRhdGEgCiAgICAgICAgICAgICBXSEVSRSBzb3JyeSA9IDEKICAgICAgICAgICAgIE9SREVSIEJZIGZyZXEgREVTQyIpCmBgYAoKV2hhdCBhYm91dCBhIGdyYW5kIHRvdGFsOgoKYGBge3J9CiNVc2Ugc3VtIHRvIGFkZCB1cCBhbGwgdGhlIHJlc3VsdHMgYW5kIGp1c3Qgc2hvdyB0aGF0CnNxbGRmOjpzcWxkZigiU0VMRUNUIHN1bShmcmVxKSwgc29ycnkgRlJPTSBjbGVhbmVkX3R3ZWV0ZGF0YSAKICAgICAgICAgICAgIEdST1VQIEJZIHNvcnJ5CiAgICAgICAgICAgICBPUkRFUiBCWSBmcmVxIERFU0MiKQpgYGAKClRob3NlIHRvdGFscyByZWZlciB0byBpbmRpdmlkdWFsIHdvcmRzIHJhdGhlciB0aGFuIHR3ZWV0cywgc28gd2UgY291bGQgcmVwZWF0IGl0IGZvciB0aGUgdHdlZXRzIGluc3RlYWQ6CgpgYGB7cn0KI0dlbmVyYXRlIGEgVFJVRS9GQUxTRSBjb2x1bW4gd2hpY2ggcmV0dXJucyB0cnVlIGlmICdzb3JyeScgb3IgJ2Fwb2wnIGlzIGFueXdoZXJlIGluIHRoZSB0ZXh0CnR3ZWV0c25vdjIwb24kc29ycnkgPC0gZ3JlcGwoInNvcnJ5fGFwb2wiLCBpZ25vcmUuY2FzZSA9IFQsIHR3ZWV0c25vdjIwb24kdHdlZXR0eHQpCiNCZWNhdXNlICdzb3JyeScgaXMgYSBUUlVFL0ZBTFNFIGxvZ2ljYWwgY29sdW1uLCB3ZSBuZWVkIHRvIHVzZSAxIG9yIDAgdG8gaW5kaWNhdGUgdGhhdCBpbiBzcWxkZgojU2VlIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQxNDMzOTI3L3NxbGRmLWNhbnQtcmVhZC1sb2dpY2FsLXZlY3RvcnMtaW4tcgpzb3JyeXRvdGFscyA8LSBzcWxkZjo6c3FsZGYoIlNFTEVDVCBjb3VudCgqKSwgc29ycnkgRlJPTSB0d2VldHNub3YyMG9uIAogICAgICAgICAgICAgR1JPVVAgQlkgc29ycnkKICAgICAgICAgICAgICIpCnNvcnJ5dG90YWxzCmBgYAoKQXMgYSBwZXJjZW50YWdlIG9mIHR3ZWV0cy4uLgoKYGBge3J9CnNvcnJ5dG90YWxzJGBjb3VudCgqKWBbMl0vc3VtKHNvcnJ5dG90YWxzJGBjb3VudCgqKWApKjEwMApgYGAKCgojIyBBIGJyZWFrZG93biBieSBhY2NvdW50CgpXZSBjYW4gZ3JvdXAgYnkgYWNjb3VudCB0byBzZWUgd2hpY2ggb25lcyBhcmUgbW9zdCBzb3JyeToKCmBgYHtyfQpzb3JyeWJ5YWNjb3VudCA8LSBzcWxkZjo6c3FsZGYoIlNFTEVDVCBjb3VudCgqKSwgYWNjb3VudCwgc29ycnkgRlJPTSB0d2VldHNub3YyMG9uCiAgICAgICAgICAgICBHUk9VUCBCWSBhY2NvdW50LCBzb3JyeQogICAgICAgICAgICAgIikKc29ycnlieWFjY291bnQKd3JpdGVfY3N2KHNvcnJ5YnlhY2NvdW50LCAic29ycnlieWFjY291bnQuY3N2IikKYGBgCgpUaGUgd29yc3QgaXMgTm9ydGhlcm4sIHNvIGxldCdzIGltcG9ydCB0aGF0IGRhdGEsIHRoZW4gZXhwb3J0IGl0IGFzIGEgQ1NWIHRvIGJlIGFuYWx5c2VkIG1hbnVhbGx5IGluIEV4Y2VsIGlmIHdlIG5lZWQ6CgpgYGB7cn0Kbm9ydGhlcm5qc29uIDwtICJodHRwczovL3ByZW1pdW0uc2NyYXBlcndpa2kuY29tLzNlcGFiMnkvaXlqaWxobnZ4bzY5ZG5wL3NxbC8/cT0lMjBzZWxlY3QlMjAqJTBBJTIwZnJvbSUyMHRyYWluY29tcGFueXR3ZWV0cyUyMCUwQSUyMHdoZXJlJTIwYWNjb3VudCUyMGlzJTIwJTIybm9ydGhlcm5hc3Npc3QlMjIlMEElMjAiCm5vcnRoZXJudHdlZXRzIDwtIGpzb25saXRlOjpmcm9tSlNPTihub3J0aGVybmpzb24pCndyaXRlX2Nzdihub3J0aGVybnR3ZWV0cywibm9ydGhlcm50d2VldHMuY3N2IikKYGBgCgojIyMgVGhlIHdvcnN0IGRheSBvZiB0aGUgd2VlawoKT3IgdGhlIHdvcnN0IGRheSBvZiB0aGUgd2VlawoKYGBge3J9CiNMRUZUIGlzIGNhbGxlZCBMRUZUU1RSIGluIHNxbGRmOiBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8zMTg0MzM3My9ob3ctdG8tdXNlLXJpZ2h0LWxlZnQtdG8tc3BsaXQtYS12YXJpYWJsZS1pbi1zcWxkZi1hcy1pbi1sZWZ0eC1uCnNvcnJ5Ynl3ZWVrZGF5IDwtIHNxbGRmOjpzcWxkZigiU0VMRUNUIGNvdW50KCopLCBMRUZUU1RSKHR3ZWV0ZGF0ZSwgMykgQVMgdHdlZXRkYXksIHNvcnJ5IEZST00gdHdlZXRzbm92MjBvbgogICAgICAgICAgICAgR1JPVVAgQlkgdHdlZXRkYXksIHNvcnJ5CiAgICAgICAgICAgICAiKQpzb3JyeWJ5d2Vla2RheQp3cml0ZV9jc3Yoc29ycnlieXdlZWtkYXksICJzb3JyeWJ5d2Vla2RheS5jc3YiKQpgYGAKCiMjIEEgYnJlYWtkb3duIG92ZXIgdGltZQoKV2UgY2FuIHVzZSB0aGUgc2FtZSBhcHByb2FjaCB0byBwdWxsIG91dCBlYWNoIG1vbnRoJ3MgYXBvbG9naWVzOgoKYGBge3J9CiNTVUJTVFIgaXMgdGhlIHNxbGRmIGZ1bmN0aW9uIHRvIGV4dHJhY3QgZnJvbSB0aGUgbWlkZGxlIG9mIHRleHQsIGxpa2UgTUlEIGluIEV4Y2VsCnNvcnJ5Ynltb250aCA8LSBzcWxkZjo6c3FsZGYoIlNFTEVDVCBjb3VudCgqKSwgU1VCU1RSKHR3ZWV0ZGF0ZSwgNSwzKSBBUyB0d2VldGRheSwgc29ycnkgRlJPTSB0d2VldHNub3YyMG9uCiAgICAgICAgICAgICBHUk9VUCBCWSB0d2VldGRheSwgc29ycnkKICAgICAgICAgICAgICIpCnNvcnJ5Ynltb250aAp3cml0ZV9jc3Yoc29ycnlieW1vbnRoLCAic29ycnlieW1vbnRoLmNzdiIpCmBgYAoKVGhlcmUgYXJlIDEyIG1vbnRocyB0aGVyZSwgc28gdGhhdCBtZWFucyBkaWZmZXJlbnQgYWNjb3VudHMnIHR3ZWV0cyBjb3ZlciBkaWZmZXJlbnQgcGVyaW9kczoKCmBgYHtyfQojU1VCU1RSIGlzIHRoZSBzcWxkZiBmdW5jdGlvbiB0byBleHRyYWN0IGZyb20gdGhlIG1pZGRsZSBvZiB0ZXh0LCBsaWtlIE1JRCBpbiBFeGNlbAphY2NvdW50Ynltb250aCA8LSBzcWxkZjo6c3FsZGYoIlNFTEVDVCBjb3VudCgqKSwgU1VCU1RSKHR3ZWV0ZGF0ZSwgNSwzKSBBUyB0d2VldGRheSwgYWNjb3VudCBGUk9NIHR3ZWV0c25vdjIwb24KICAgICAgICAgICAgIEdST1VQIEJZIHR3ZWV0ZGF5LCBhY2NvdW50IE9SREVSIGJ5IGFjY291bnQKICAgICAgICAgICAgICIpCmFjY291bnRieW1vbnRoCndyaXRlX2NzdihhY2NvdW50Ynltb250aCwgImFjY291bnRieW1vbnRoLmNzdiIpCmBgYAoKIyMgT3RoZXIgd29yZHM6IGNyZXcsIGxhdGUsIGxvbmRvbgoKTGV0J3MgYWRkIGEgbmV3IGNvbHVtbiB0aGF0IGxvb2tzIGZvciAnY3Jldycgb3IgJ3N0YWZmJwoKYGBge3J9CiNHZW5lcmF0ZSBhIFRSVUUvRkFMU0UgY29sdW1uIHdoaWNoIHJldHVybnMgdHJ1ZSBpZiAnY3Jldycgb3IgJ3N0YWZmJyBpcyBhbnl3aGVyZSBpbiB0aGUgdGV4dAp0d2VldHNub3YyMG9uJGNyZXcgPC0gZ3JlcGwoImNyZXd8c3RhZmYiLCBpZ25vcmUuY2FzZSA9IFQsdHdlZXRzbm92MjBvbiR0d2VldHR4dCkKY3Jld3RvdGFscyA8LSBzcWxkZjo6c3FsZGYoIlNFTEVDVCBjb3VudCgqKSwgY3JldyBGUk9NIHR3ZWV0c25vdjIwb24gCiAgICAgICAgICAgICBHUk9VUCBCWSBjcmV3CiAgICAgICAgICAgICAiKQpjcmV3dG90YWxzCmBgYAoKT3IgJ2xhdGUnIG9yICdkZWxheScKCmBgYHtyfQoKdHdlZXRzbm92MjBvbiRsYXRlIDwtIGdyZXBsKCJsYXRlfGRlbGF5IiwgaWdub3JlLmNhc2UgPSBULCB0d2VldHNub3YyMG9uJHR3ZWV0dHh0KQpsYXRldG90YWxzIDwtIHNxbGRmOjpzcWxkZigiU0VMRUNUIGNvdW50KCopLCBsYXRlIEZST00gdHdlZXRzbm92MjBvbiAKICAgICAgICAgICAgIEdST1VQIEJZIGxhdGUKICAgICAgICAgICAgICIpCmxhdGV0b3RhbHMKYGBgCgoKT3IgJ2ZhdWx0JyBvciAncHJvYmxlbScKCmBgYHtyfQp0d2VldHNub3YyMG9uJGZhdWx0IDwtIGdyZXBsKCJmYXVsdHxwcm9ibGVtIiwgaWdub3JlLmNhc2UgPSBULCB0d2VldHNub3YyMG9uJHR3ZWV0dHh0KQpmYXVsdHRvdGFscyA8LSBzcWxkZjo6c3FsZGYoIlNFTEVDVCBjb3VudCgqKSwgZmF1bHQgRlJPTSB0d2VldHNub3YyMG9uIAogICAgICAgICAgICAgR1JPVVAgQlkgZmF1bHQKICAgICAgICAgICAgICIpCmZhdWx0dG90YWxzCmBgYAoKT3IgJ2NhcnJpYWdlJyBvciAnY29hY2hlcycKCmBgYHtyfQp0d2VldHNub3YyMG9uJGNhcnJpYWdlIDwtIGdyZXBsKCJjYXJyaWFnZXxjb2FjaCIsIGlnbm9yZS5jYXNlID0gVCwgdHdlZXRzbm92MjBvbiR0d2VldHR4dCkKY2FycmlhZ2V0b3RhbHMgPC0gc3FsZGY6OnNxbGRmKCJTRUxFQ1QgY291bnQoKiksIGNhcnJpYWdlIEZST00gdHdlZXRzbm92MjBvbiAKICAgICAgICAgICAgIEdST1VQIEJZIGNhcnJpYWdlCiAgICAgICAgICAgICAiKQpjYXJyaWFnZXRvdGFscwpgYGAKCgoKT3IgJ2NvbXBlbnNhdGlvbicgb3IgJ3JlcGF5JyAoZGVsYXkgcmVwYXkpCgpgYGB7cn0KI0dlbmVyYXRlIGEgVFJVRS9GQUxTRSBjb2x1bW4gd2hpY2ggcmV0dXJucyB0cnVlIGlmICdjcmV3JyBvciAnc3RhZmYnIGlzIGFueXdoZXJlIGluIHRoZSB0ZXh0CnR3ZWV0c25vdjIwb24kY29tcGVuc2F0aW9uIDwtIGdyZXBsKCJjb21wZW5zYXR8cmVwYXkiLCBpZ25vcmUuY2FzZSA9IFQsIHR3ZWV0c25vdjIwb24kdHdlZXR0eHQpCmNvbXBlbnNhdGlvbnRvdGFscyA8LSBzcWxkZjo6c3FsZGYoIlNFTEVDVCBjb3VudCgqKSwgY29tcGVuc2F0aW9uIEZST00gdHdlZXRzbm92MjBvbiAKICAgICAgICAgICAgIEdST1VQIEJZIGNvbXBlbnNhdGlvbgogICAgICAgICAgICAgIikKY29tcGVuc2F0aW9udG90YWxzCmBgYAoKCgpPciAnbG9uZG9uJyAKCmBgYHtyfQp0d2VldHNub3YyMG9uJGxvbmRvbiA8LSBncmVwbCgib25kb24iLCBpZ25vcmUuY2FzZSA9IFQsIHR3ZWV0c25vdjIwb24kdHdlZXR0eHQpCmxvbmRvbnRvdGFscyA8LSBzcWxkZjo6c3FsZGYoIlNFTEVDVCBjb3VudCgqKSwgbG9uZG9uIEZST00gdHdlZXRzbm92MjBvbiAKICAgICAgICAgICAgIEdST1VQIEJZIGxvbmRvbgogICAgICAgICAgICAgIikKbG9uZG9udG90YWxzCmBgYAoKT3IgJ3ZhbmRhbGlzbScgCgpgYGB7cn0KdHdlZXRzbm92MjBvbiR2YW5kYWwgPC0gZ3JlcGwoInZhbmRhbCIsIGlnbm9yZS5jYXNlID0gVCwgdHdlZXRzbm92MjBvbiR0d2VldHR4dCkKdmFuZGFsdG90YWxzIDwtIHNxbGRmOjpzcWxkZigiU0VMRUNUIGNvdW50KCopLCB2YW5kYWwgRlJPTSB0d2VldHNub3YyMG9uIAogICAgICAgICAgICAgR1JPVVAgQlkgdmFuZGFsCiAgICAgICAgICAgICAiKQp2YW5kYWx0b3RhbHMKYGBgCgojIyBDb21wZW5zYXRpb246IGEgYnJlYWtkb3duIGJ5IGFjY291bnQKClBhcnQgb2Ygb3VyIHN0b3J5IGlzIGFib3V0IGNvbXBlbnNhdGlvbiwgc28gaXQgbWlnaHQgYmUgdXNlZnVsIHRvIGdyb3VwIGJ5IGFjY291bnQgdG8gc2VlIHdoaWNoIG9uZXMgbWVudGlvbiB0aGF0IG1vc3Q6CgpgYGB7cn0KY29tcGVuc2F0aW9uYnlhY2NvdW50IDwtIHNxbGRmOjpzcWxkZigiU0VMRUNUIGNvdW50KCopLCBhY2NvdW50LCBjb21wZW5zYXRpb24gRlJPTSB0d2VldHNub3YyMG9uCiAgICAgICAgICAgICBHUk9VUCBCWSBhY2NvdW50LCBjb21wZW5zYXRpb24KICAgICAgICAgICAgICIpCmNvbXBlbnNhdGlvbmJ5YWNjb3VudAp3cml0ZV9jc3YoY29tcGVuc2F0aW9uYnlhY2NvdW50LCAiY29tcGVuc2F0aW9uYnlhY2NvdW50LmNzdiIpCmBgYA==